﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="AsyncEventTaskBinding_When_From.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Defines the AsyncEventTaskBinding_When_From type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Threading
{
    using System;
    using System.Threading;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Photon.ComponentModel;
    using Photon.Threading.EventPattern;
    using Testing;

    [TestClass]
   public class AsyncEventTaskBinding_When_From
   {
      private AsyncEventSource _asyncEventSource;

      public interface IFuncExplicitIntf 
      {
         void FuncExplicitIntfAsync(int arg1, object userState);
         event EventHandler<AsyncCompletedEventArgs<int>> FuncExplicitIntfCompleted;
      }

      public class AsyncEventSource : IFuncExplicitIntf
      {
         private void RunFunc(object userState, params int[] args)
         {
            if (DoCompleted != null)
               DoCompleted(this, new AsyncCompletedEventArgs<int[]>(args, null, false, userState));
         }

         public void DoWithNoCompletedAsync(object userState) {}

         public void DoWithCompletedOfWrongTypeAsync(object userState)
         {
            
         }

#pragma warning disable 67
         public event EventHandler<EventArgs> FuncWithCompletedOfWrongTypeCompleted;
#pragma warning restore 67

         public void DoAsync(object userState)
         {
            RunFunc(userState);
         }

         public void DoAsync(int arg1, object userState)
         {
            RunFunc(userState, arg1);
         }

         public void DoAsync(int arg1, int arg2, object userState)
         {
            RunFunc(userState, arg1, arg2);
         }

         public void DoAsync(int arg1, int arg2, int arg3, object userState)
         {
            RunFunc(userState, arg1, arg2, arg3);
         }

         public event EventHandler<AsyncCompletedEventArgs<int[]>> DoCompleted;

         public void DoWithErrorAsync(int arg1, object userState)
         {
            if (DoWithErrorCompleted != null)
               DoWithErrorCompleted(this, new AsyncCompletedEventArgs<int>(0, new Exception("DoWithErrorAsync"), false, userState));
         }

         public event EventHandler<AsyncCompletedEventArgs<int>> DoWithErrorCompleted;

         public void FuncWithCancelAsync(int arg1, object userState)
         {
            ThreadPool.QueueUserWorkItem(
               s =>
                  {
                     if (FuncWithCancelCompleted != null)
                        FuncWithCancelCompleted(this, new AsyncCompletedEventArgs<int>(0, null, true, s));
                  }
               , userState);
         }

         public event EventHandler<AsyncCompletedEventArgs<int>> FuncWithCancelCompleted;

         public static void FuncStaticAsync(int arg1, object userState)
         {
            ThreadPool.QueueUserWorkItem(
               s =>
                  {
                     if (FuncStaticCompleted != null)
                        FuncStaticCompleted(null, new AsyncCompletedEventArgs<int>(arg1, null, true, s));
                  }
               , userState);
         }

         public static event EventHandler<AsyncCompletedEventArgs<int>> FuncStaticCompleted;

         #region IFuncExplicitIntf Members

         void IFuncExplicitIntf.FuncExplicitIntfAsync(int arg1, object userState)
         {
            ThreadPool.QueueUserWorkItem(
               s =>
                  {
                     if (_funcExplicitIntfCompleted != null)
                        _funcExplicitIntfCompleted(null, new AsyncCompletedEventArgs<int>(arg1, null, true, s));
                  }
               , userState);
         }

         private event EventHandler<AsyncCompletedEventArgs<int>> _funcExplicitIntfCompleted;
         
         event EventHandler<AsyncCompletedEventArgs<int>> IFuncExplicitIntf.FuncExplicitIntfCompleted
         {
            add { _funcExplicitIntfCompleted += value; }
            remove { _funcExplicitIntfCompleted -= value; }
         }

         #endregion
      }

      [TestInitialize]
      public void Initialize()
      {
         _asyncEventSource = new AsyncEventSource();
      }

      [TestMethod]
      public void From_Func_With_0_Args()
      {
         var binding = AsyncEventTaskBinding.From(_asyncEventSource.DoAsync, 
            (AsyncCompletedEventArgs<int[]> args) => args.Result);
         AssertBindingResult(binding);
      }
      
      [TestMethod]
      public void From_Func_With_1_Args()
      {
         var binding = AsyncEventTaskBinding.From(_asyncEventSource.DoAsync, 1,
            (AsyncCompletedEventArgs<int[]> args) => args.Result);
         AssertBindingResult(binding, 1);
      }

      [TestMethod]
      public void From_Func_With_2_Args()
      {
         var binding = AsyncEventTaskBinding.From(_asyncEventSource.DoAsync, 1, 2,
            (AsyncCompletedEventArgs<int[]> args) => args.Result);
         AssertBindingResult(binding, 1, 2);
      }

      [TestMethod]
      public void From_Func_With_3_Args()
      {
         var binding = AsyncEventTaskBinding.From(_asyncEventSource.DoAsync, 1, 2, 3,
            (AsyncCompletedEventArgs<int[]> args) => args.Result);
         AssertBindingResult(binding, 1, 2, 3);
      }
      
      [TestMethod]
      public void From_Lambda_Func_Should_Throw_ArgumentException()
      {
         Action<object> action = o => { };
         new MethodTest(() => AsyncEventTaskBinding.From(action))
            .AssertThrowsArgumentException(0);
      }

      [TestMethod]
      public void From_Func_That_Does_Not_Have_A_Corresponding_Completed_Event_Should_Throw_ArgumentException()
      {
         new MethodTest(() => AsyncEventTaskBinding.From(_asyncEventSource.DoWithNoCompletedAsync))
            .AssertThrowsArgumentException(0);
      }

      [TestMethod]
      public void From_Func_Whose_Corresponding_Completed_Event_Is_Of_The_Wrong_Type_Should_Throw_ArgumentException()
      {
         new MethodTest(() => AsyncEventTaskBinding.From(_asyncEventSource.DoWithCompletedOfWrongTypeAsync))
            .AssertThrowsArgumentException(0);
      }
      
      [TestMethod]
      public void From_Func_Whose_Event_Returns_And_Error_Should_Propogate_Error()
      {
          var task = AsyncEventPatternExtensions.FromAsyncEvent(Task.Factory, _asyncEventSource.DoWithErrorAsync, 1, 
            (AsyncCompletedEventArgs<int> a) => a.Result);
         task.Start(null);
         Assert.IsNotNull(task.Error);
         Assert.AreEqual("FuncWithErrorAsync", task.Error.Message);
      }

      [TestMethod]
      public void From_Static_Func()
      {
         var task = Task.Factory.FromAsyncEvent(AsyncEventSource.FuncStaticAsync, 1,
            (AsyncCompletedEventArgs<int> a) => a.Result);
         task.Start(null);
         Assert.AreEqual(1, task.Result);
      }

      [TestMethod]
      public void From_Func_Bound_To_An_Explicit_Interface_Implementation()
      {
         var source = _asyncEventSource as IFuncExplicitIntf;
         var task = Task.Factory.FromAsyncEvent<int, AsyncCompletedEventArgs<int>, int>(source.FuncExplicitIntfAsync, 1,
            (AsyncCompletedEventArgs<int> a) => a.Result);
         task.Start(null);
         Assert.AreEqual(1, task.Result);
      }

      [TestMethod]
      public void From_Func_Whose_Event_Returns_Canceled_Should_Propogate_Cancel()
      {
         var task = Task.Factory.FromAsyncEvent<int, AsyncCompletedEventArgs<int>, int>(_asyncEventSource.FuncWithCancelAsync, 1,
            (AsyncCompletedEventArgs<int> a) => a.Result);
         task.Start(null);
         Assert.AreEqual(0, task.Result);
         Assert.IsNull(task.Error);
         Assert.IsTrue(task.IsCanceled);
      }
      
      private static void AssertBindingResult(AsyncEventTaskBinding<AsyncCompletedEventArgs<int[]>, int[]>
         binding, params int[] expected)
      {
         int[] result = null;
         binding.Begin(c => result = binding.End(null));
         AssertResultSequence(expected, result);
      }

      private static void AssertResultSequence(int[] expected, int[] actual)
      {
         Assert.AreEqual(expected.Length, actual.Length);
         for (var i=0; i<expected.Length; i++)
            Assert.AreEqual(expected[i], actual[i], "Items different at index {0}.", i);
      }
   }
}